package xuan.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.Color;
import java.awt.Insets;

import java.util.LinkedList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.JCheckBox;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JSplitPane;
import javax.swing.BoxLayout;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import xuan.lib.*;

import org.roblybarger.*;

/**
 * main frame of merge interface
 * - display operations, document tree and document content
 * - receive and respond to user actions
 * 
 * @author xuan
 *
 */

@SuppressWarnings("serial")
public class InteractiveMergePanel extends JPanel {
	// Default files
	public static final String EDITLOG = "tmp/edit.log";
	public static final String CONFLICTLOG = "tmp/conflict.log";
	
	private String strSource;//string path of the source file
	private String strMerged;//string path of the merged file by 3DM
	private String strEditLog;//string path of the edit log output by 3DM
	private Document source;//internal tree of the source file
	private EditLog editlog;//the edit log
	private Merge merge;
	
	private JTree docTree;
	private XMLTreeModel model;
	private JScrollPane operationsScrollPane;
	private JPanel operationsPanel;
	private JScrollPane textScrollPane;
	private JEditorPane textPane;
	private JSplitPane verticalSplitPane;
	private JSplitPane horizontalSplitPane;
	private List<JCheckBox> opList = new LinkedList<JCheckBox>();
	
	public InteractiveMergePanel(String[] agrs) {
		
		setAutoscrolls(true);		
		setMinimumSize(new Dimension(800, 600));
		setPreferredSize(new Dimension(800, 600));
		setLayout(new BorderLayout());				
		
		operationsPanel = new JPanel();
		operationsPanel.setAutoscrolls(true);
		operationsPanel.setBackground(Color.WHITE);
		
		FlowLayout flowLayout = (FlowLayout) operationsPanel.getLayout();
		flowLayout.setAlignment(FlowLayout.LEFT);
		
		verticalSplitPane = new JSplitPane();
		verticalSplitPane.setResizeWeight(0.5);
		verticalSplitPane.setAutoscrolls(true);
		verticalSplitPane.setPreferredSize(new Dimension(800, 300));
		verticalSplitPane.setDividerSize(5);
		
		operationsPanel.setLayout(new javax.swing.BoxLayout(operationsPanel, javax.swing.BoxLayout.Y_AXIS));
		operationsScrollPane = new JScrollPane(operationsPanel);
		operationsScrollPane.setAutoscrolls(true);
		operationsScrollPane.setPreferredSize(new Dimension(300, 300));
		
		model = new XMLTreeModel();
		docTree = new JTree();
		docTree.setModel(model);
		docTree.setShowsRootHandles(true);
		docTree.setEditable(false);
		
		JScrollPane treePane = new JScrollPane(docTree);
		treePane.setAutoscrolls(true);
		treePane.setPreferredSize(new Dimension(500, 300));
		
		JLabel lblDocumentTree = new JLabel("Document Tree");
		lblDocumentTree.setBackground(Color.WHITE);
		treePane.setColumnHeaderView(lblDocumentTree);
		
		verticalSplitPane.setLeftComponent(operationsScrollPane);		
		verticalSplitPane.setRightComponent(treePane);
		verticalSplitPane.setDividerLocation(300);
		
		textScrollPane = new JScrollPane();
		textScrollPane.setAutoscrolls(true);
		textScrollPane.setPreferredSize(new Dimension(800, 300));
		
		textPane = new JEditorPane();
		textPane.setEditable(false);
		textPane.setContentType("text/html");
		textPane.setEditorKit(new HTMLEditorKit());
		
		//make intern link operational
		textPane.addHyperlinkListener(new HyperlinkListener() {
			public void hyperlinkUpdate(HyperlinkEvent e) {
				if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
					JEditorPane src = (JEditorPane)e.getSource();
					if (e.getDescription().startsWith("#")) {
						src.scrollToReference(e.getDescription().substring(1));
					}
				}				
			}
		});
		

		textScrollPane.setViewportView(textPane);		
		JLabel lblTextNode = new JLabel("Text Content");
		lblTextNode.setBackground(Color.WHITE);
		textScrollPane.setColumnHeaderView(lblTextNode);
		
		horizontalSplitPane = new JSplitPane();
		horizontalSplitPane.setResizeWeight(0.5);
		horizontalSplitPane.setAutoscrolls(true);
		horizontalSplitPane.setPreferredSize(new Dimension(800, 600));
		horizontalSplitPane.setDividerSize(5);
		
		horizontalSplitPane.setOrientation(JSplitPane.VERTICAL_SPLIT);
		add(horizontalSplitPane, "Center");
		horizontalSplitPane.setTopComponent(verticalSplitPane);
		horizontalSplitPane.setBottomComponent(textScrollPane);
		horizontalSplitPane.setDividerLocation(300);
		
			
		//by clicking on a tree cell, its text content will be displayed within text editor
		docTree.addTreeSelectionListener(new TreeSelectionListener() {
			public void valueChanged(TreeSelectionEvent e) {
				Object lpc = e.getPath().getLastPathComponent();
				if (lpc instanceof XMLTreeNode) {															
					textPane.setText(DocUtils.XML2HTML(((XMLTreeNode)lpc).getElement()));
					textPane.setCaretPosition(0);
				}
			}
		});
	}
	
	
	/**
	 * load interface with data from merge model
	 * 
	 * @param base string path of the source file, 
	 * @param merged string path of the target file
	 */
	public void load(String base, String merged){	
		strSource = base;
		strMerged = merged;
		strEditLog = EDITLOG;
		
		try{
			merge = new Merge(new String[]{strSource,strMerged,strEditLog});
			removeAll();
			source = merge.getSource();
			editlog = merge.getELog();
			model.setDocument(source);
			setOpeartionsPane();
			docTree.setCellRenderer(new TreeCellCustomRenderer(null, null,null,false));
			TreeUtils.expandAll(docTree);
			textPane.setText(DocUtils.XML2HTML(source.getDocumentElement()));
			textPane.setCaretPosition(0);
		}
		catch(Exception e){
			JOptionPane.showMessageDialog(this, "file not found or incompatible format");
		}
	}

	/**
	 * reload interface with initial data
	 */
	public void reload(){
		try{
			merge = new Merge(new String[]{strSource,strMerged,strEditLog});
			source = merge.getSource();
			editlog = merge.getELog();
			model.setDocument(source);
			setOpeartionsPane();
			docTree.setCellRenderer(new TreeCellCustomRenderer(null, null,null,false));
			TreeUtils.expandAll(docTree);
			textPane.setText(DocUtils.XML2HTML(source.getDocumentElement()));
			textPane.setCaretPosition(0);
		}
		catch(Exception e){}
	}
	
	
	/**
	 * save internal tree in XML file with a given name
	 * @param fileName string path of file to save
	 */
	public void save(String fileName) throws Exception{
		DocUtils.exportXMLFile(source,"xsl/unhideInlineTags.xsl",fileName);			
	}

	@Override
	public void removeAll(){
		operationsPanel.removeAll();
		textPane.removeAll();
		docTree.removeAll();
	}

	/**
	 * display all not-removed operations within operations pane
	 */
	private void setOpeartionsPane() {
		List<Operation> edits = editlog.get();
		opList.clear();
		operationsPanel.removeAll();
		
		for(int i=0;i<edits.size();i++){
			Operation operation = edits.get(i);
			JCheckBox checkBox = new JCheckBox(operation.toString());
			checkBox.setBackground(Color.WHITE);			
			
			opList.add(checkBox);	
			operationsPanel.add(checkBox);
			checkBox.addItemListener(new CheckedItem() );
			
			//set style of each operation up to its status
			if(operation.isEnabled()){
				checkBox.setEnabled(true);
			}
			else{
				checkBox.setEnabled(false);
			}
			
			if(operation.isExecuted() && !checkBox.isSelected()){
				checkBox.setSelected(true);
			}
			
			if(!operation.isExecuted() && checkBox.isSelected()){
				checkBox.setSelected(false);
			}
			
			if(operation.isConfirmed()){
				checkBox.setEnabled(false);
			}
		}
		
		operationsPanel.repaint();
		operationsPanel.revalidate();
	}


	/**
	 * preview one operation, evoked when an operation is selected 
	 */
	private void preview(Operation op) {
		merge.preview(op);
		model.reload();
		docTree.collapseRow(0);
		TreeCellCustomRenderer renderer;
		Object insert, delete, update;
		
		switch(op.getType()){
		case INSERT:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				insert = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(insert, null,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);							
			}
			else{
				insert = new XMLTreeNode((Element)op.getSourceNode());
				renderer = new TreeCellCustomRenderer(insert, null,null, false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)insert);
			break;
		case DELETE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				delete = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(null, delete,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);		
			}
			else{
				delete = new XMLTreeNode((Element)op.getSourceNode());
				renderer = new TreeCellCustomRenderer(null,delete,null,false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)delete);
			break;
		case UPDATE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				update = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(null, null,update,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			else{
				update = new XMLTreeNode((Element)op.getTempNode());				
				renderer = new TreeCellCustomRenderer(null, null,update,false);
				docTree.setCellRenderer(renderer);
			}
			TreeUtils.expandOneNode(docTree, (XMLTreeNode)update);
			break;
		case MOVE:
			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
				insert = new XMLTreeNode((Element)op.getTargetNode());
				delete = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
				renderer = new TreeCellCustomRenderer(insert,delete,null,true);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			else{
				insert = new XMLTreeNode((Element)op.getTempNode());
				delete = new XMLTreeNode((Element)op.getSourceNode());				
				renderer = new TreeCellCustomRenderer(insert, delete,null,false);
				docTree.setCellRenderer(renderer);
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
				textPane.setCaretPosition(0);
			}
			
			TreeUtils.expandTwoNodes(docTree, (XMLTreeNode)insert, (XMLTreeNode)delete);			
			break;
//		default://COPY
//			if(op.getSourceNode().getNodeType()==Node.TEXT_NODE || op.getSourceNode().getNodeType()==Node.CDATA_SECTION_NODE){
//				insert = new XMLTreeNode((Element)op.getTargetNode());
//				update = new XMLTreeNode((Element)op.getSourceNode().getParentNode());
//				renderer = new TreeCellCustomRenderer(insert,null,update,true);
//				docTree.setCellRenderer(renderer);
//				textPane.setText(op.getSourceNode().getTextContent());
//				textPane.setForeground(null);
//			}
//			else{
//				insert = new XMLTreeNode((Element)op.getTempNode());
//				update = new XMLTreeNode((Element)op.getSourceNode());
//				renderer = new TreeCellCustomRenderer(insert, null,update,false);
//				docTree.setCellRenderer(renderer);
//				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement(), op));
//				textPane.setCaretPosition(0);
//			}
		}
		
		merge.execute(op);
		refreshOperations();
	}

	/**
	 * refresh operations panel after the confirmation of user
	 */
	private void refreshOperations() {
		JCheckBox checkBox;
//		boolean finished=true;
		
		for(int i=0; i< opList.size();i++){
			checkBox = opList.get(i);
			checkBox.setText(editlog.getOperation(i).toString());
			Operation operation = editlog.getOperation(i);
			if(operation.isEnabled()){
				checkBox.setEnabled(true);
			}
			else{
				checkBox.setEnabled(false);
			}
			if(operation.isExecuted() && !checkBox.isSelected()){
				checkBox.setSelected(true);
			}
			if(!operation.isExecuted() && checkBox.isSelected()){
				checkBox.setSelected(false);
			}
			
			if(operation.isConfirmed()){
				checkBox.setEnabled(false);
			}
//			finished = finished && editlog.getOperation(i).isExecuted();
		}
	}
	
	/**
	 * Class replying user action selecting an operation
	 */
	class CheckedItem implements ItemListener{
		public void itemStateChanged(ItemEvent itemEvent) {
//			AbstractButton abstractButton = (AbstractButton)itemEvent.getSource();
	        int state = itemEvent.getStateChange();
	       
	        int indexOp = operationsPanel.getComponentZOrder((Component) itemEvent.getItem());	        	
        	Operation op = editlog.getOperation(indexOp);        	        	
        	
	        if (state == ItemEvent.SELECTED) {	        	
	        	preview(op);
	        	if(operationsScrollPane.getColumnHeader()==null) operationsScrollPane.setColumnHeaderView(new ConfirmPanel());	    		
	        }
	        if(state == ItemEvent.DESELECTED){
	        	merge.undo(op);
	        	refreshOperations();
				model.reload();
				docTree.setCellRenderer(new TreeCellCustomRenderer(null, null, null, false));
				textPane.setText(DocUtils.XML2HTML(source.getDocumentElement()));
				textPane.setCaretPosition(0);
	        }
		}
	}

	/**
	 * class representing the confirmation pop-up
	 */
	class ConfirmPanel extends JPanel implements ActionListener{
		private JButton btnApply;
		private JButton btnIgnore;
		private JButton btnCancel;
		
		public ConfirmPanel() {
			setBackground(new Color(173, 216, 230));
			setLayout(new BoxLayout(this, BoxLayout.X_AXIS));		
			JLabel lblNewLabel = new JLabel("Confirm ?    ");
			lblNewLabel.setMinimumSize(new Dimension(40, 15));
			lblNewLabel.setPreferredSize(new Dimension(40, 15));
			add(lblNewLabel);		
			btnApply = new JButton("Apply");
			btnApply.setMargin(new Insets(2, 5, 2, 5));
			btnApply.addActionListener(this);
			add(btnApply);
			JLabel label = new JLabel("  ");
			add(label);		
			btnIgnore = new JButton("Ignore");
			btnIgnore.setMargin(new Insets(2, 5, 2, 5));		
			JLabel label_1 = new JLabel("  ");
			btnIgnore.addActionListener(this);
			add(btnIgnore);
			add(label_1);		
			btnCancel = new JButton("Cancel");
			btnCancel.setMargin(new Insets(2, 5, 2, 5));
			btnCancel.addActionListener(this);
			add(btnCancel);
			
		}
		
		public void actionPerformed(ActionEvent evt) {
			Object button = evt.getSource();
			
			if (button == btnApply) {
				merge.apply();
				refreshOperations();
			}
			else if(button == btnIgnore){				
				merge.ignore();
				model.reload();
				setOpeartionsPane();
			}
			else{
				merge.cancel();
				model.reload();
				refreshOperations();
			}
			
			docTree.setCellRenderer(new TreeCellCustomRenderer(null, null, null, false));
			textPane.setText(DocUtils.XML2HTML(source.getDocumentElement()));
			textPane.setCaretPosition(0);
			operationsScrollPane.setColumnHeader(null);
			
			removeAll();
			repaint();
			revalidate();
		}
	}
}
